#ifndef __COLOR_H__
#define __COLOR_H__

namespace inf {
    class Color3 {
    public:	
        float			r;
        float			g;
        float			b;

        Color3();
        explicit		Color3( const float r, const float g, const float b );
        explicit		Color3( const Vec3& rhs );

        void 			set( const float r, const float g, const float b );
        void			zero();

        void			normalize( float scaleFactor = 1.0f );

        void			to_bytes( byte* bytes ) const;
        void			from_bytes( byte* bytes );

        Vec3&			to_Vec3();
        const Vec3&		to_Vec3() const;


        int				get_dimension() const;

        float			operator[]( const int index ) const;
        float& 			operator[]( const int index );
        Color3			operator-() const;
        Color3			operator*( const float rhs ) const;
        Color3			operator/( const float rhs ) const;
        Color3			operator+( const Color3& rhs ) const;
        Color3			operator-( const Color3& rhs ) const;
        Color3& 		operator+=( const Color3& rhs );
        Color3& 		operator-=( const Color3& rhs );
        Color3& 		operator/=( const Color3& rhs );
        Color3& 		operator/=( const float rhs );
        Color3& 		operator*=( const float rhs );	

        Color3& 		operator=( const Vec3& rhs );

        friend Color3	operator*( const float a1, const Color3 b1 );

        bool			compare( const Color3& rhs ) const;							// exact compare, no epsilon
        bool			compare( const Color3& rhs, const float epsilon ) const;		// compare with epsilon
        bool			operator==(	const Color3& rhs ) const;						// exact compare, no epsilon
        bool			operator!=(	const Color3& rhs ) const;						// exact compare, no epsilon

        const float*	to_float_ptr() const;
        float*			to_float_ptr();
        const char*		to_string( int precision = 2 ) const;

        void			lerp( const Color3& v1, const Color3& v2, const float l );

        // packs color floats in the range [0,1] into an integer
        static dword		pack_Color( const Vec3& color );
        static void			unpack_Color( const dword color, Vec3& unpackedColor );

        static	const Color3 black;
        static	const Color3 white;
        static	const Color3 red;
        static	const Color3 green;
        static	const Color3 blue;
        static	const Color3 yellow;
        static	const Color3 magenta;
        static	const Color3 cyan;
        static	const Color3 orange;
        static	const Color3 purple;
        static	const Color3 pink;
        static	const Color3 brown;
        static	const Color3 ltGrey;
        static	const Color3 mdGrey;
        static	const Color3 dkGrey;

        static	const Color3 ltBlue;
        static	const Color3 dkRed;
    };

    INF_INLINE Color3::Color3( void ) {
    }

    INF_INLINE Color3::Color3( const float r, const float g, const float b ) {
        this->r = r;
        this->g = g;
        this->b = b;
    }

    INF_INLINE Color3::Color3( const Vec3& rhs ) {
        this->r = rhs.x;
        this->g = rhs.y;
        this->b = rhs.z;
    }

    INF_INLINE void Color3::to_bytes( byte* bytes ) const {
        for( int i = 0; i < get_dimension(); i++ ) {
            bytes[ i ] = math::ftob( (*this)[ i ] * 255.0f );
        }
    }

    INF_INLINE void Color3::from_bytes( byte* bytes ) {
        for( int i = 0; i < get_dimension(); i++ ) {
            (*this)[ i ] = bytes[ i ] / 255.0f;
        }
    }

    INF_INLINE void Color3::set( const float r, const float g, const float b ) {
        this->r = r;
        this->g = g;
        this->b = b;
    }

    INF_INLINE void Color3::normalize( float scaleFactor ) {
        r = math::clamp_float( 0.0f, 1.0f, r * scaleFactor );
        g = math::clamp_float( 0.0f, 1.0f, g * scaleFactor );
        b = math::clamp_float( 0.0f, 1.0f, b * scaleFactor );
    }

    INF_INLINE void Color3::zero( void ) {
        r = g = b = 0.0f;
    }

    INF_INLINE int Color3::get_dimension( void ) const {
        return 3;
    }

    INF_INLINE float Color3::operator[]( int index ) const {
        assert( index >= 0 && index < 3 );
        return ( &r )[ index ];
    }

    INF_INLINE float& Color3::operator[]( int index ) {
        assert( index >= 0 && index < 3 );
        return ( &r )[ index ];
    }

    INF_INLINE Color3 Color3::operator-( void ) const {
        return Color3( -r, -g, -b );
    }

    INF_INLINE Color3 Color3::operator-( const Color3& rhs ) const {
        return Color3( r - rhs.r, g - rhs.g, b - rhs.b );
    }

    INF_INLINE Color3 Color3::operator*( const float rhs ) const {
        return Color3( r * rhs, g * rhs, b * rhs );
    }

    INF_INLINE Color3 Color3::operator/( const float rhs ) const {
        float inva = 1.0f / rhs;
        return Color3( r * inva, g * inva, b * inva );
    }

    INF_INLINE Color3 operator*( const float a1, const Color3 b1 ) {
        return Color3( b1.r * a1, b1.g * a1, b1.b * a1 );
    }

    INF_INLINE Color3 Color3::operator+( const Color3& rhs ) const {
        return Color3( r + rhs.r, g + rhs.g, b + rhs.b );
    }

    INF_INLINE Color3& Color3::operator+=( const Color3& rhs ) {
        r += rhs.r;
        g += rhs.g;
        b += rhs.b;

        return *this;
    }

    INF_INLINE Color3& Color3::operator/=( const Color3& rhs ) {
        r /= rhs.r;
        g /= rhs.g;
        b /= rhs.b;

        return *this;
    }

    INF_INLINE Color3& Color3::operator/=( const float rhs ) {
        float inva = 1.0f / rhs;
        r *= inva;
        g *= inva;
        b *= inva;

        return *this;
    }

    INF_INLINE Color3& Color3::operator-=( const Color3& rhs ) {
        r -= rhs.r;
        g -= rhs.g;
        b -= rhs.b;

        return *this;
    }

    INF_INLINE Color3& Color3::operator*=( const float rhs ) {
        r *= rhs;
        g *= rhs;
        b *= rhs;

        return *this;
    }

    INF_INLINE bool Color3::compare( const Color3& rhs ) const {
        return ( ( r == rhs.r ) && ( g == rhs.g ) && ( b == rhs.b ) );
    }

    INF_INLINE bool Color3::compare( const Color3& rhs, const float epsilon ) const {
        if( math::fabs( r - rhs.r ) > epsilon ) {
            return false;
        }

        if ( math::fabs( g - rhs.g ) > epsilon ) {
            return false;
        }

        if ( math::fabs( b - rhs.b ) > epsilon ) {
            return false;
        }

        return true;
    }

    INF_INLINE bool Color3::operator==( const Color3& rhs ) const {
        return compare( rhs );
    }

    INF_INLINE bool Color3::operator!=( const Color3& rhs ) const {
        return !compare( rhs );
    }

    INF_INLINE const float* Color3::to_float_ptr( void ) const {
        return &r;
    }

    INF_INLINE float* Color3::to_float_ptr( void ) {
        return &r;
    }

    INF_INLINE Color3& Color3::operator=( const Vec3& rhs ) {
        r = rhs.x;
        g = rhs.y;
        b = rhs.z;
        return *this;
    }

    INF_INLINE const Vec3&	Color3::to_Vec3( void ) const {
        return *reinterpret_cast< const Vec3 * >( this );
    }

    INF_INLINE Vec3&	Color3::to_Vec3( void ) {
        return *reinterpret_cast< Vec3 * >( this );
    }





    class Color4 {
    public:	
        float			r;
        float			g;
        float			b;
        float			a;

        Color4();
        explicit		Color4( const float r, const float g, const float b, const float a );
        explicit		Color4( const Vec4& rhs );

        void 			set( const float r, const float g, const float b, const float a );
        void			zero();

        void			normalize( float scaleFactor = 1.0f );

        int				get_dimension() const;

        void			to_bytes( byte* bytes ) const;
        void			from_bytes( byte* bytes );

        Vec4&			to_Vec4();
        const Vec4&		to_Vec4() const;

        float			operator[]( const int index ) const;
        float& 			operator[]( const int index );
        Color4		operator-() const;
        Color4		operator*( const float rhs ) const;
        Color4		operator/( const float rhs ) const;
        Color4		operator+( const Color4& rhs ) const;
        Color4		operator-( const Color4& rhs ) const;
        Color4& 		operator+=( const Color4& rhs );
        Color4& 		operator-=( const Color4& rhs );
        Color4& 		operator/=( const Color4& rhs );
        Color4& 		operator/=( const float rhs );
        Color4& 		operator*=( const float rhs );	

        Color4& 		operator=( const Vec4& rhs );

        friend Color4	operator*( const float a1, const Color4 b1 );

        bool			compare( const Color4& rhs ) const;							// exact compare, no epsilon
        bool			compare( const Color4& rhs, const float epsilon ) const;		// compare with epsilon
        bool			operator==(	const Color4& rhs ) const;						// exact compare, no epsilon
        bool			operator!=(	const Color4& rhs ) const;						// exact compare, no epsilon

        const float*	to_float_ptr() const;
        float*			to_float_ptr();
        const char*		to_string( int precision = 2 ) const;

        void			lerp( const Color4& v1, const Color4& v2, const float l );

        static dword	pack_Color( const Vec3& color, float alpha );
        static dword	pack_Color( const Vec4& color );
        static void		unpack_Color( const dword color, Vec4& unpackedColor );

        const Color3&	to_Color3() const;
        Color3&		    to_Color3();

        static	const Color4 black;
        static	const Color4 white;
        static	const Color4 red;
        static	const Color4 green;
        static	const Color4 blue;
        static	const Color4 yellow;
        static	const Color4 magenta;
        static	const Color4 cyan;
        static	const Color4 orange;
        static	const Color4 purple;
        static	const Color4 pink;
        static	const Color4 brown;
        static	const Color4 ltGrey;
        static	const Color4 mdGrey;
        static	const Color4 dkGrey;

        static	const Color4 ltBlue;
        static	const Color4 dkRed;
    };

    INF_INLINE Color4::Color4( void ) {
    }

    INF_INLINE Color4::Color4( const float r, const float g, const float b, const float a ) {
        this->r = r;
        this->g = g;
        this->b = b;
        this->a = a;
    }

    INF_INLINE Color4::Color4( const Vec4& rhs ) {
        this->r = rhs.x;
        this->g = rhs.y;
        this->b = rhs.z;
        this->a = rhs.w;
    }

    INF_INLINE void Color4::to_bytes( byte* bytes ) const {
        for( int i = 0; i < get_dimension(); i++ ) {
            bytes[ i ] = math::ftob( (*this)[ i ] * 255.0f );
        }
    }

    INF_INLINE void Color4::from_bytes( byte* bytes ) {
        for( int i = 0; i < get_dimension(); i++ ) {
            (*this)[ i ] = bytes[ i ] / 255.0f;
        }
    }

    INF_INLINE void Color4::set( const float r, const float g, const float b, const float a ) {
        this->r = r;
        this->g = g;
        this->b = b;
        this->a = a;
    }

    INF_INLINE void Color4::normalize( float scaleFactor ) {
        r = math::clamp_float( 0.0f, 1.0f, r * scaleFactor );
        g = math::clamp_float( 0.0f, 1.0f, g * scaleFactor );
        b = math::clamp_float( 0.0f, 1.0f, b * scaleFactor );
        a = math::clamp_float( 0.0f, 1.0f, a * scaleFactor );
    }

    INF_INLINE void Color4::zero( void ) {
        r = g = b = a = 0.0f;
    }

    INF_INLINE int Color4::get_dimension( void ) const {
        return 4;
    }

    INF_INLINE float Color4::operator[]( int index ) const {
        assert( index >= 0 && index < 4 );
        return ( &r )[ index ];
    }

    INF_INLINE float& Color4::operator[]( int index ) {
        assert( index >= 0 && index < 4 );
        return ( &r )[ index ];
    }

    INF_INLINE Color4 Color4::operator-( void ) const {
        return Color4( -r, -g, -b, -a );
    }

    INF_INLINE Color4 Color4::operator-( const Color4& rhs ) const {
        return Color4( r - rhs.r, g - rhs.g, b - rhs.b, a - rhs.a );
    }

    INF_INLINE Color4 Color4::operator*( const float rhs ) const {
        return Color4( r * rhs, g * rhs, b * rhs, a * rhs );
    }

    INF_INLINE Color4 Color4::operator/( const float rhs ) const {
        float inva = 1.0f / rhs;
        return Color4( r * inva, g * inva, b * inva, a * inva );
    }

    INF_INLINE Color4 operator*( const float a1, const Color4 b1 ) {
        return Color4( b1.r * a1, b1.g * a1, b1.b * a1, b1.a * a1 );
    }

    INF_INLINE Color4 Color4::operator+( const Color4& rhs ) const {
        return Color4( r + rhs.r, g + rhs.g, b + rhs.b, a + rhs.a );
    }

    INF_INLINE Color4& Color4::operator+=( const Color4& rhs ) {
        r += rhs.r;
        g += rhs.g;
        b += rhs.b;
        a += rhs.a;

        return *this;
    }

    INF_INLINE Color4& Color4::operator/=( const Color4& rhs ) {
        r /= rhs.r;
        g /= rhs.g;
        b /= rhs.b;
        a /= rhs.a;

        return *this;
    }

    INF_INLINE Color4& Color4::operator/=( const float rhs ) {
        float inva = 1.0f / rhs;
        r *= inva;
        g *= inva;
        b *= inva;
        a *= inva;

        return *this;
    }

    INF_INLINE Color4& Color4::operator-=( const Color4& rhs ) {
        r -= rhs.r;
        g -= rhs.g;
        b -= rhs.b;
        a -= rhs.a;

        return *this;
    }

    INF_INLINE Color4& Color4::operator*=( const float rhs ) {
        r *= rhs;
        g *= rhs;
        b *= rhs;
        a *= rhs;

        return *this;
    }

    INF_INLINE bool Color4::compare( const Color4& rhs ) const {
        return ( ( r == rhs.r ) && ( g == rhs.g ) && ( b == rhs.b ) && a == rhs.a );
    }

    INF_INLINE bool Color4::compare( const Color4& rhs, const float epsilon ) const {
        if ( math::fabs( r - rhs.r ) > epsilon ) {
            return false;
        }

        if ( math::fabs( g - rhs.g ) > epsilon ) {
            return false;
        }

        if ( math::fabs( b - rhs.b ) > epsilon ) {
            return false;
        }

        if ( math::fabs( a - rhs.a ) > epsilon ) {
            return false;
        }

        return true;
    }

    INF_INLINE bool Color4::operator==( const Color4& rhs ) const {
        return compare( rhs );
    }

    INF_INLINE bool Color4::operator!=( const Color4& rhs ) const {
        return !compare( rhs );
    }

    INF_INLINE const float* Color4::to_float_ptr( void ) const {
        return &r;
    }

    INF_INLINE float* Color4::to_float_ptr( void ) {
        return &r;
    }

    INF_INLINE Color4& Color4::operator=( const Vec4& rhs ) {
        r = rhs.x;
        g = rhs.y;
        b = rhs.z;
        a = rhs.w;
        return *this;
    }

    INF_INLINE const Color3 &Color4::to_Color3( void ) const {
        return *reinterpret_cast< const Color3 * >( this );
    }

    INF_INLINE Color3 &Color4::to_Color3( void ) {
        return *reinterpret_cast< Color3 * >( this );
    }

    INF_INLINE const Vec4&	Color4::to_Vec4( void ) const {
        return *reinterpret_cast< const Vec4 * >( this );
    }

    INF_INLINE Vec4&	Color4::to_Vec4( void ) {
        return *reinterpret_cast< Vec4 * >( this );
    }

}   // ::inf


#endif // __COLOR_H__
